package static_resource

import (
	"fmt"
	"gitee.com/go-web-2/web_framework/context"
	lru "github.com/hashicorp/golang-lru"
	"io"
	"os"
	"path/filepath"
	"strconv"
)

type StaticResource struct {
	dir     string
	accepts map[string]string
	cache   *lru.Cache
	// 缓存单文件大小上限
	maxSize int
}

func (s *StaticResource) validateExt(path string) (*fileItem, error) {
	fileName := filepath.Base(path)
	ext := filepath.Ext(fileName)
	accept, ok := s.accepts[ext[1:]]
	if !ok {
		return nil, fmt.Errorf("不支持的文件类型：%s\n", ext)
	}
	return &fileItem{
		name:        fileName,
		contextType: accept,
	}, nil
}

func (s *StaticResource) readFile(path string) ([]byte, error) {
	path = filepath.Join(s.dir, path)
	f, err := os.Open(path)
	if err != nil {
		return []byte{}, err
	}
	defer f.Close()
	var data []byte
	data, err = io.ReadAll(f)
	if err != nil {
		return []byte{}, err
	}
	return data, nil
}

func (s *StaticResource) handleRespFile(c *context.Context, fi *fileItem) {
	c.W.Header().Add("Content-Type", fmt.Sprintf("%s; charset=utf-8", fi.contextType))
	c.W.Header().Add("Content-Length", strconv.Itoa(fi.size))
	c.RespOK(fi.data)
}

func (s *StaticResource) HandleFunc(c *context.Context) {
	path := c.RouteDynamic
	fi, err := s.validateExt(path)
	if err != nil {
		c.RespSystemErr(err)
		return
	}

	// 判断缓存是否开启
	if s.cache != nil {
		// 先在缓存中查找资源
		// 若找到就返回；若未找到则读取文件，返回并写入缓存
		file, ok := s.cache.Get(fi.name)
		if ok {
			s.handleRespFile(c, file.(*fileItem))
			return
		}

		fi, err = s.readFileAndResp(c, path, fi)
		if err != nil {
			c.RespSystemErr(err)
			return
		}

		if fi.size > s.maxSize && s.cache != nil {
			fmt.Printf("文件超大无法写入缓存，文件路径：%s\n", path)
			return
		}
		s.cache.Add(fi.name, fi)

	}

	fi, err = s.readFileAndResp(c, path, fi)
	if err != nil {
		c.RespSystemErr(err)
		return
	}

}

func (s *StaticResource) readFileAndResp(c *context.Context, path string, fi *fileItem) (*fileItem, error) {
	fileBs, err := s.readFile(path)
	if err != nil {
		return nil, err
	}
	fi.data = fileBs
	fi.size = len(fileBs)
	s.handleRespFile(c, fi)
	return fi, nil
}

type Opt func(s *StaticResource)

func WithCache(maxCount int, maxSize int) Opt {
	return func(s *StaticResource) {
		var err error
		s.cache, err = lru.New(maxCount)
		if err != nil {
			fmt.Printf("静态资源缓存创建失败：%s\n", err)
			return
		}
		s.maxSize = maxSize
	}
}

func WithAccepts(accepts map[string]string) Opt {
	return func(s *StaticResource) {
		for k, v := range accepts {
			s.accepts[k] = v
		}
	}
}

func NewStaticResource(dir string, opts ...Opt) *StaticResource {
	s := &StaticResource{
		dir: dir,
		accepts: map[string]string{
			"png":  "image/png",
			"jpg":  "image/jpg",
			"jpeg": "image/jpeg",
			"html": "text/html",
		},
	}
	for _, opt := range opts {
		opt(s)
	}

	return s
}

type fileItem struct {
	name        string
	size        int
	data        []byte
	contextType string
}
